﻿// <copyright file="LogicalExpressionReader.cs" company="http://www.dymetis.com">
//
// Copyright (c) 2011 Eddie Z. Lin.
//
// This source is subject to the Microsoft Public License.
// See http://www.opensource.org/licenses/ms-pl.
// All other rights reserved.
// 
// </copyright>

namespace Dymetis.Expressions
{
    using System;
    using System.Linq;
    using System.Text;

    internal sealed class LogicalExpressionReader
    {
        private readonly string input;
        private readonly StringBuilder currentSpelling;
        private readonly LogicalExpressionSyntax syntax;
        private int position = -1;
        private char currentCharacter;

        public LogicalExpressionReader(string input, LogicalExpressionSyntax syntax)
        {
           // Contract.Requires<ArithmeticException>(!String.IsNullOrWhiteSpace(input));
            this.input = input;
            this.currentSpelling = new StringBuilder();
            this.syntax = syntax;

            this.ReadCharacter();
        }

        private bool Eof
        {
            get
            {
                return this.position >= this.input.Length;
            }
        }

        public LogicalExpressionToken CreateToken(LogicalExpressionTokenType tokenType, bool takeSpelling = false)
        {
            string spelling = null;

            if (takeSpelling)
            {
                spelling = String.Intern(this.currentSpelling.ToString());
            }

            this.currentSpelling.Clear();
            return new LogicalExpressionToken(tokenType, spelling);
        }

        public LogicalExpressionToken ReadToken()
        {
            // Skip whitespaces.
            while (Char.IsWhiteSpace(this.currentCharacter) && this.ReadCharacter())
            {
            }

            // No more token found.
            if (this.Eof)
            {
                return null;
            }

            if (this.currentCharacter == this.syntax.StringDelimiter || this.currentCharacter == this.syntax.AltStringDelimiter)
            {
                var quote = this.currentCharacter;

                this.ReadCharacter();

                while (this.currentCharacter != quote)
                {
                    if (this.currentCharacter == '\n' || !this.TakeCharacter())
                    {
                        throw new Exception("New line in string literal");
                    }
                }

                this.ReadCharacter();

                return this.CreateToken(LogicalExpressionTokenType.LiteralString, true);
            }
            else if (this.currentCharacter == this.syntax.VariableIndicator)
            {
                this.ReadCharacter();
                return this.CreateToken(LogicalExpressionTokenType.VariableIndicator, false);
            }
            else if (this.currentCharacter == this.syntax.GroupBegin)
            {
                this.ReadCharacter();
                return this.CreateToken(LogicalExpressionTokenType.ParenthesisLeft);
            }
            else if (this.currentCharacter == this.syntax.GroupEnd)
            {
                this.ReadCharacter();
                return this.CreateToken(LogicalExpressionTokenType.ParenthesisRight);
            }
            else
            {
                int currentIndex = 0;
                var possibleTokens = this.syntax.NonLiteralTokens.AsQueryable();
                int possibleTokenCount = possibleTokens.Count();
                while (possibleTokenCount > 1)
                {
                    var filteredTokens = possibleTokens
                        .Where(t => t.Key.Length > currentIndex && t.Key[currentIndex] == this.currentCharacter)
                        .ToList()
                        .AsQueryable();

                    possibleTokenCount = filteredTokens.Count();
                    if (possibleTokenCount == 0 && possibleTokens.Count(t => t.Key == this.currentSpelling.ToString()) == 1)
                    {
                        possibleTokens = possibleTokens.Where(t => t.Key == this.currentSpelling.ToString()).ToList().AsQueryable();
                        this.currentSpelling.Clear();
                        return possibleTokens.Single().Value;
                    }

                    if (possibleTokenCount > 0)
                    {
                        ++currentIndex;
                        this.TakeCharacter();
                    }

                    possibleTokens = filteredTokens;
                }

                if (currentIndex > 0)
                {
                    this.TakeCharacter();
                    this.currentSpelling.Clear();
                    return possibleTokens.Single().Value;
                }
            }

            // Digits.
            if (Char.IsDigit(this.currentCharacter))
            {
                bool isDecimal = false;
                while (this.TakeCharacter() && (Char.IsDigit(this.currentCharacter) || this.currentCharacter == '.'))
                {
                    if (this.currentCharacter == '.')
                    {
                        isDecimal = true;
                    }
                }

                return this.CreateToken(isDecimal ? LogicalExpressionTokenType.LiteralDecimal : LogicalExpressionTokenType.LiteralInteger, true);
            }

            if (!Char.IsLetter(this.currentCharacter))
            {
                throw new Exception("Syntax error");
            }

            while (Char.IsLetterOrDigit(this.currentCharacter) && this.TakeCharacter())
            {
            }

            var literal = this.currentSpelling.ToString().ToLowerInvariant();

            var literalToken = this.syntax.LiteralTokens.Where(t => t.Key == literal).Select(t => t.Value).FirstOrDefault();

            if (literalToken == null)
            {
                return this.CreateToken(LogicalExpressionTokenType.LiteralString, true);
            }
            else
            {
                this.currentSpelling.Clear();
                return literalToken;
            }
        }

        private bool ReadCharacter()
        {
            ++this.position;
            if (!this.Eof)
            {
                this.currentCharacter = this.input[this.position];
                return true;
            }

            return false;
        }

        private bool TakeCharacter()
        {
            this.currentSpelling.Append(this.currentCharacter);

            return this.ReadCharacter();
        }
    }
}
